/*
 * @file iso9660.c
 *
 *   ISO9660 helpers
 */
#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>

#include <rtems.h>
#include <rtems/libio.h>
#include <rtems/bdbuf.h>
#include <rtems/seterr.h>

#include "iso9660.h"
#include "iso9660_io.h"
#include "iso9660_susp.h"
#include "iso9660_dbg.h"
#include "iso9660_shell.h"


/**
 *   Initialization of the ISO9660 volume descriptor
 *
 * @param mt_entry  Mount table entry
 * @param version   Supported ISO9660 version and extensions
 */
int
iso9660_init_volume_info(
    rtems_filesystem_mount_table_entry_t    *mt_entry,
    iso9660_version_t                        version
)
{
  int                    rc = 0;
  iso9660_fs_info_t     *fs = mt_entry->fs_info;
  struct stat           host_volume;
  char                  sector_buffer[ISO9660_SECTOR_SIZE];

  /* Check the existence of the host volume */
  rc = stat(mt_entry->dev, &host_volume);
  if (rc == -1)
    rtems_iso9660_error_and_return_minus_one(errno,
        "init_volume: failed to stat the host volume.");

  if (S_ISBLK(host_volume.st_mode))
    rc = iso9660_buffer_open(&host_volume, fs);
  else if (S_ISREG(host_volume.st_mode) || S_ISLNK(host_volume.st_mode))
    rc = iso9660_regular_open(mt_entry->dev, &host_volume, fs);
  else
    /* Not supported .. */
    rtems_iso9660_error_and_return_minus_one(EBADF,
        "init_volume: host volume is neither a file nor a block device.");

  /* Something went wrong */
  if (rc)
    rtems_iso9660_error_and_return_minus_one(errno,
        "init_volume: failed to open host volume.");

  fs->version_mask = version;
  fs->version = ISO9660_STANDARD_1988;

  /* Read the primary volume descriptor */
  rc = iso9660_read_superblock(mt_entry, (iso9660_descriptor_t*)sector_buffer, version);
  if (rc)
    rtems_iso9660_error_and_return_minus_one(EINVAL,
        "init_volume: failed to read superblock");

  /* Fill the FS volume informations */
  fs->volume.space_size = ISO9660_PVD_VOLUME_SPACE_SIZE(sector_buffer);
  fs->volume.set_size = ISO9660_PVD_VOLUME_SET_SIZE(sector_buffer);
  fs->volume.sequence_num = ISO9660_PVD_VOLUME_SEQ_NUMBER(sector_buffer);
  fs->volume.logical_block_size = ISO9660_PVD_LOGICAL_BLOCK_SIZE(sector_buffer);
  fs->volume.path_table_size = ISO9660_PVD_PATH_TABLE_SIZE(sector_buffer);
  fs->volume.l_path_table = ISO9660_PVD_L_PATH_TABLE(sector_buffer);
  fs->volume.l_path_table_2 = ISO9660_PVD_L_PATH_TABLE2(sector_buffer);
  fs->volume.m_path_table = ISO9660_PVD_M_PATH_TABLE(sector_buffer);
  fs->volume.m_path_table_2 = ISO9660_PVD_M_PATH_TABLE2(sector_buffer);
  fs->volume.root_record_location = ISO9660_RECORD_LOCATION(ISO9660_PVD_ROOT_DIRECTORY_RECORD(sector_buffer));

  rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_FSMOUNT,
        "Root record: \n");
  rtems_iso9660_dbg_string(ISO9660_PVD_ROOT_DIRECTORY_RECORD(sector_buffer), 34);

  rc = strnlen(ISO9660_PVD_SYSTEM_ID(sector_buffer), ISO9660_PVD_SYSTEM_ID_LEN);
  fs->volume.system_identifier = malloc(rc + 1);
  if (fs->volume.system_identifier == NULL)
    rtems_iso9660_error_and_return_minus_one(ENOMEM,
        "init_volume: failed to initialize system identifier.");

  memcpy(fs->volume.system_identifier, ISO9660_PVD_SYSTEM_ID(sector_buffer), rc);
  fs->volume.system_identifier[rc] = '\0';

  rc = strnlen(ISO9660_PVD_VOLUME_ID(sector_buffer), ISO9660_PVD_VOLUME_ID_LEN);
  fs->volume.volume_identifier = malloc(rc + 1);
  if (fs->volume.volume_identifier == NULL)
  {
    free(fs->volume.system_identifier);
    rtems_iso9660_error_and_return_minus_one(ENOMEM,
        "init_volume: failed to initialize volume identifier.");
  }

  memcpy(fs->volume.volume_identifier, ISO9660_PVD_VOLUME_ID(sector_buffer), rc);
  fs->volume.volume_identifier[rc] = '\0';

  /*
   * Check SUSP/RRIP capability
   */
  iso9660_susp_update_fs_info(fs);
  return 0;
}


/**
 *   Read the various descriptors in the iso9660 volume descriptor set
 * and determine which volume descriptor is the most "powerful" according
 * to the given supported version.
 *
 *   The fallback descriptor is always the PVD which shall be present in any
 * case. If that descriptor is not found it's a serious error of the volume.
 *
 * @param mt_entry    Mount table entry
 * @param superblock  Pointer to a buffer to be filled with the determined
 *                    descriptor, it could be a PVD, an SVD or an EVD.
 * @param version     Supported ISO9660 version
 *
 * @return 0 if the buffer contains the valid superblock, -1 if an error occured
 */
int
iso9660_read_superblock(
    rtems_filesystem_mount_table_entry_t    *mt_entry,
    iso9660_descriptor_t                    *superblock,
    iso9660_version_t                        version
)
{
  iso9660_fs_info_t *fs = mt_entry->fs_info;
  char descriptor[ISO9660_SECTOR_SIZE];
  iso9660_sector_t descriptor_num = 0;
  int32_t superblock_num = -1;
  int rc;

  /*   Read the descriptor set and look for the most capable
   * according to 'version'.
   */
  do
  {
    rc = iso9660_read_descriptor(mt_entry, (iso9660_descriptor_t*)descriptor,
        descriptor_num);

    if (rc)
      rtems_iso9660_error_and_return_minus_one(EINVAL,
          "read_superblock: invalid descriptor.");

    rc = iso9660_update_version((iso9660_descriptor_t*)descriptor,
        &fs->version);

    switch (ISO9660_VOLUME_DESC_TYPE(descriptor))
    {
      case ISO9660_PRIMARY_VOLUME_DESCRIPTOR:
        /* The PVD is the fallback descriptor in all case */
        if (superblock_num == -1)
          superblock_num = descriptor_num;
        break;

      case ISO9660_ENHANCED_VOLUME_DESCRIPTOR:
        /* EVD indicate an ISO9660:1999 volume in all cases */
        if (iso9660_version_supports(version, ISO9660_STANDARD_1999))
          superblock_num = descriptor_num;

      case ISO9660_SUPPLEMENTARY_VOLUME_DESCRIPTOR:
        rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_EXTENSIONS,
            "Checking whether specified version supports Joliet ... %s \n",
            iso9660_version_supports(version, ISO9660_EXTENSION_JOLIET)?"yes":"no");
        rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_EXTENSIONS,
            "Checking whether iso9660 volume supports Joliet ... %s \n",
            iso9660_version_supports(fs->version, ISO9660_EXTENSION_JOLIET)?"yes":"no");

        if (iso9660_version_supports(fs->version, ISO9660_EXTENSION_JOLIET)
            && iso9660_version_supports(version, ISO9660_EXTENSION_JOLIET))
           superblock_num = descriptor_num;
        break;

      default:
        break;
    }

    descriptor_num++;
  } while (ISO9660_VOLUME_DESC_TYPE(descriptor) != ISO9660_SET_TERMINATOR_DESCRIPTOR);

  if (superblock_num == -1)
    rtems_iso9660_error_and_return_minus_one(EINVAL,
        "read_superblock: unable to find a correct superblock.");


  rc = iso9660_read_descriptor(mt_entry, superblock, superblock_num);
  if (rc)
    rtems_iso9660_error_and_return_minus_one(EIO,
        "read_superblock: failed to read superblock.");

  return 0;
}


/**
 *   Read the specified descriptor from the ISO9660 volume descriptor set.
 * The sector is numbered relatively to the first descriptor, located after
 * the first ISO9660_SYSTEM_USE_SECTORS reserved sectors.
 *
 * @param fs          A pointer to the iso9660 filesystem information structure
 * @param descriptor  The output buffer
 * @param sector      Sector number relative to the first descriptor sector
 *
 * @return 0 if the descriptor is successfully read, -1 if an error occured
 */
int
iso9660_read_descriptor(
    rtems_filesystem_mount_table_entry_t    *mt_entry,
    iso9660_descriptor_t                    *descriptor,
    iso9660_sector_t                         sector
)
{
  iso9660_fs_info_t *fs = mt_entry->fs_info;
  int rc;

  rc = iso9660_read_sector(fs, ISO9660_SYSTEM_USE_SECTORS + sector, 0,
      ISO9660_SECTOR_SIZE, descriptor);

  if (rc != ISO9660_SECTOR_SIZE)
    rtems_iso9660_error_and_return_minus_one(EIO,
        "read_descriptor: failed to read descriptor.");

  /* Check the descriptor validity */
  rc = iso9660_check_descriptor(descriptor);
  if (rc < 0)
    rtems_iso9660_error_and_return_minus_one(EIO,
        "read_descriptor: descriptor is invalid.");

  return 0;
}


/**
 *   Check the ISO9660 descriptor structure validity. Every descriptor
 * can be cast-ed to an iso9660_descriptor_t.
 *
 * @param descriptor      Pointer to the 2048 byte sector buffer of the
 *                        descriptor
 *
 * @return 0 if the descriptor is valid, -1 if something is wrong
 */
int
iso9660_check_descriptor(
    iso9660_descriptor_t *descriptor
)
{
  char *standard_id = ISO9660_VOLUME_DESC_ID(descriptor);

  /* The standard identifier should be "CD001" (ECMA-119 p15) */
  if ( standard_id[0] != 'C'
    || standard_id[1] != 'D'
    || standard_id[2] != '0'
    || standard_id[3] != '0'
    || standard_id[4] != '1')
    rtems_iso9660_error_and_return_minus_one(EINVAL,
        "check_descriptor: invalid standard identifier.");

  /* Unrecognized protocol version ? */
  switch (ISO9660_VOLUME_DESC_TYPE(descriptor))
  {
    case ISO9660_BOOT_RECORD_DESCRIPTOR:
    case ISO9660_PRIMARY_VOLUME_DESCRIPTOR:
    case ISO9660_PARTITION_DESCRIPTOR:
    case ISO9660_SET_TERMINATOR_DESCRIPTOR:
      if (ISO9660_VOLUME_DESC_VERSION(descriptor) != 1)
        rtems_iso9660_error_and_return_minus_one(EINVAL,
            "check_descriptor: unhandled standard version.");
      break;

    case ISO9660_SUPPLEMENTARY_VOLUME_DESCRIPTOR:
      /* Version 2 of the SVD implies an enhanced volume descriptor */
      if (ISO9660_VOLUME_DESC_VERSION(descriptor) == 2)
        ISO9660_SET_VOLUME_DESC_TYPE(descriptor, ISO9660_ENHANCED_VOLUME_DESCRIPTOR);
      else if (ISO9660_VOLUME_DESC_VERSION(descriptor) != 1)
        rtems_iso9660_error_and_return_minus_one(EINVAL,
            "check_descriptor: unhandled standard version.");
      break;

    default:
      rtems_iso9660_error_and_return_minus_one(EINVAL,
          "check_descriptor: unhandled descriptor.");
      break;
  }

  return 0;
}


/**
 *   Update the given version according to the descriptor data.
 *
 * @param descriptor      Pointer to the 2048 byte sector buffer of the
 *                        descriptor
 * @param volume_version  Pointer to an iso9660_version_t to be completed
 *                        according to this descriptor data.
 *
 * @return 0 if the update is OK
 */
int
iso9660_update_version(
    iso9660_descriptor_t     *descriptor,
    iso9660_version_t        *volume_version
)
{
  rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_EXTENSIONS,
      "Updating version from descriptor type=%d\n", ISO9660_VOLUME_DESC_TYPE(descriptor));

  switch (ISO9660_VOLUME_DESC_TYPE(descriptor))
  {
    case ISO9660_ENHANCED_VOLUME_DESCRIPTOR:
    case ISO9660_SUPPLEMENTARY_VOLUME_DESCRIPTOR:
    {
      char *escape_sequences = ISO9660_SVD_ESCAPE_SEQUENCES(descriptor);
      rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_EXTENSIONS,
          " .. version of EVD/SVD : %d\n", ISO9660_VOLUME_DESC_VERSION(descriptor));

      /* Version 2 of the SVD implies an enhanced volume descriptor */
      if (ISO9660_VOLUME_DESC_VERSION(descriptor) == 2)
      {
        /* Reset the actual standard version */
        *volume_version &= ISO9660_VERSION_EXTENSION_MASK;
        *volume_version |= ISO9660_STANDARD_1999;
      }

      rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_EXTENSIONS,
          " .. non-standard escape seq. : %s\n",
          ISO9660_SVD_VOLUME_FLAGS(descriptor)?"true":"false");
      rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_EXTENSIONS,
          " .. escape seq. : %s\n", escape_sequences);

      /* Joliet volume is identified by UCS-2 encoding */
      if (!(ISO9660_SVD_VOLUME_FLAGS(descriptor) & ISO9660_NON_STANDARD_ESCAPE_SEQUENCE)
          && (escape_sequences[0] == '%' && escape_sequences[1] == '/'))
      {
        switch (escape_sequences[2])
        {
          case '@':
            *volume_version |= ISO9660_EXTENSION_JOLIET_LEVEL1;
            break;

          case 'C':
            *volume_version |= ISO9660_EXTENSION_JOLIET_LEVEL2;
            break;

          case 'E':
            *volume_version |= ISO9660_EXTENSION_JOLIET_LEVEL3;
            break;

          default:
            break;
        }
      }
    }
      break;

    default:
      break;
  }
  return 0;
}
